package cn.akwangl.socket.bio;

import lombok.extern.slf4j.Slf4j;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Socket BIO 模型测试.Server 端实现.
 * <p>
 * 模拟多人群聊实现,{@link ChatService#executorService}是为了节省线程开销.
 *
 * @author 阿坤同学
 * @date 2022/6/18 15:05
 * @since 0.0.6
 */
@Slf4j
public class ChatService {
    public static final int DEFAULT_SERVER_PORT = 7000;

    public static final String QUIT = "quit";

    public static final String SERVER_ADDRESS = "localhost";


    private ServerSocket serverSocket;
    private List<Chat> chats;

    private ExecutorService executorService;


    public ChatService() {
        try {
            this.serverSocket = new ServerSocket(DEFAULT_SERVER_PORT);
            init();
        } catch (IOException e) {
            log.error("server socket init error", e);
            throw new RuntimeException("server socket init error", e);
        }
    }

    /**
     * 初始化.
     *
     * @author 阿坤同学
     * @date 2022/6/19 10:39
     */
    public void init() {
        // 初始化线程池
        ArrayBlockingQueue queue = new ArrayBlockingQueue(200);
        this.executorService = new ThreadPoolExecutor(5, 10, 200, TimeUnit.MILLISECONDS, queue);
        this.chats = new ArrayList(200);
    }


    public void start() {
        try {
            log.info("server 启动成功,服务端口:{},正在等待客户端连接...", DEFAULT_SERVER_PORT);
            while (true) {
                Socket clientSocket = serverSocket.accept();
                log.info("接受来自客户端:{}地址的连接,开始会话.", clientSocket.getRemoteSocketAddress());
                Chat chat = new Chat(clientSocket, this);
                registerSocket(chat);
                executorService.submit(chat);
            }
        } catch (IOException e) {
            log.error("socket 通信异常", e);
        } finally {
            close();
        }
    }


    private void removeSocket(Chat chat) {
        chats.remove(chat);
    }

    private void registerSocket(Chat chat) {
        chats.add(chat);
    }

    public void close() {
        if (!serverSocket.isClosed()) {
            try {
                executorService.shutdown();
                serverSocket.close();
                log.info("server socket 已断开连接...");
            } catch (IOException e) {
                log.error("socket 连接关闭失败", e);
            }
        }
    }

    class Chat implements Runnable {
        private Socket clientSocket;
        private DataInputStream in;
        private DataOutputStream out;
        private ChatService chatService;

        public Chat(Socket socket, ChatService chatService) {
            this.clientSocket = socket;
            this.chatService = chatService;
        }

        @Override
        public void run() {
            try {
                in = new DataInputStream(clientSocket.getInputStream());
                out = new DataOutputStream(clientSocket.getOutputStream());

                while (true) {
                    String clientMsg = readMessage();
                    log.info("clientMsg:{}", clientMsg);
                    // 如果客户端消息为退出命令则断开socket连接
                    if (QUIT.equals(clientMsg)) {
                        close();
                        break;
                    } else {
                        groupSendMessage(clientMsg);
                    }
                }
            } catch (IOException e) {
                log.error("获取socket 收发消息失败", e);
            } finally {
                close();
            }
        }

        public String readMessage() throws IOException {
            String clientMsg = in.readUTF();
            return clientMsg;
        }

        public void sendMessage(String message) throws IOException {
            out.writeUTF(message);
        }

        public void groupSendMessage(String message) throws IOException {
            for (Chat chat : chatService.chats) {
                chat.sendMessage(message);
            }
        }

        public void close() {
            if (!clientSocket.isClosed()) {
                try {
                    clientSocket.close();
                    chatService.removeSocket(this);
                    log.info("客户端:{},已断开连接.", clientSocket.getRemoteSocketAddress());
                } catch (IOException e) {
                    log.error("客户端,关闭连接失败.", e);
                }
            }
        }
    }

    public static void main(String[] args) {
        ChatService chatService = new ChatService();
        chatService.start();
    }
}
